package genetic.q4;

import java.io.File;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import genetic.GeneticAlgorithm;
import genetic.Genotype;
import genetic.Phenotype;
import genetic.q4.EternityPiece;

public class EternityPhenotype extends Phenotype{

	public static int nocrossover = 0;

	List<Integer> colors;
	List<Integer> extras;
	List<EternityPiece> forbidden;
	int fsize;
	int corner;

	public EternityPhenotype(List<Integer> colors, List<Integer> extras, List<EternityPiece> forbidden){
		this.colors = colors;
		this.extras = extras;
		this.forbidden = forbidden;
		this.fsize = colors.size();
		this.corner= colors.size()/4;
		//		System.out.println(fsize);
	}

	@Override
	public Genotype createRandomInstance(){
		List<EternityPiece> l2 = fillUpInnerFrame(new LinkedList<EternityPiece>());
		return new EternityGenotype(l2);
	}

	public List<EternityPiece> fillUpInnerFrame(List<EternityPiece> gen){
		List<EternityPiece> l;
		int color1,color2,color3=-9999999;

		int size = gen.size();
		if (size==0){
			color1 = colors.get(0);
			color2 = extras.get(0);
		} else if ((size-1)%corner == 0){
			color1 = colors.get(size-1);
			color2 = extras.get((size-1)/corner);
		} else {
			int tmp = (size-1)-(size-1)%corner;
			color1 = colors.get(tmp);
			color2 = extras.get(tmp/corner);
			while (tmp<size-1){
				color1 = gen.get(tmp).getColor(color1, 1, color2, 2) ;
				color2 = colors.get(tmp+1);
				tmp++;
			}
		}
		boolean flag = true;
		for (int i=size; flag && i<fsize; i++){
			if (i%corner == 0){
				if (i!=0){
					EternityPiece p = gen.get(i-1);
					color3 = p.getColor(color1, 1, color2, 2);
				}
				if (color1==-1 || color2==-1 || color3==-1){
					System.out.println(i+" - " + color3 + " " + color1 +" "+ color2);
					System.out.println(gen);
					System.out.println(gen.get(i-1));
				}
				color1 = colors.get(i);
				color2 = extras.get(i/corner);
				if (i!=0){
					l = compundFilter(color3, 1, color1, 2, color2, gen);	//filter again to get corners only
				} else {
					l = filter(color1, 1, color2, gen);
				}
			} else {
				EternityPiece p = gen.get(i-1);
				int tmp = color1;
				color1 = p.getColor(color1, 1, color2, 2);	//TODO problem if it has 2 colors of the same kinds
				if (color1==-1){
					System.out.println(i+" - " + color3 + " " + tmp +" "+ color2);
					System.out.println(gen);
					System.out.println(gen.get(i-1));
				}
				color2 = colors.get(i);
				l = filter(color1, 1, color2, gen);
			}
			if (i==fsize-1){
				color3 = gen.get(0).getColor(colors.get(0),1 , extras.get(0), 3);
				l = compundFilter(color1, 1, color2, 2, color3, gen);	//filter again to get corners only
			}
			flag = l.size()>0;
			if (flag){
				int random = (int)(Math.random()*l.size());
				EternityPiece curPiece = l.remove(random);
				gen.add(curPiece);
			}
		}
		return gen;
	}

	public Genotype[] crossover(Genotype g1, Genotype g2){
		EternityGenotype eg1 = (EternityGenotype)(g1);
		EternityGenotype eg2 = (EternityGenotype)(g2);
		if (eg1.getSize()==0 || eg2.getSize()==0) return new Genotype[]{g1.clone(), g2.clone()}; 


		int color11 = eg1.getPiece(0).getColor(colors.get(0), 1, extras.get(0), 2);
		int color12 = eg2.getPiece(0).getColor(colors.get(0), 1, extras.get(0), 2);
		int color2 = colors.get(1);

		List<Integer> crossoverPoints = new LinkedList<Integer>();
		for (int i=1; i<Math.min(eg1.getSize()-1, eg2.getSize()-1); i++){
			if (i%corner!=0){
				color11 = eg1.getPiece(i).getColor(color11, 1, color2, 2);
				color12 = eg2.getPiece(i).getColor(color12, 1, color2, 2);
			} else {
				color11 = eg1.getPiece(i).getColor(color11, 1, color2, 3);
				color12 = eg2.getPiece(i).getColor(color12, 1, color2, 3);
			}
			color2 = colors.get(i+1);
			if (color11==color12){
				crossoverPoints.add(i);
			}
		}
		if (!crossoverPoints.isEmpty()){
			int cross = crossoverPoints.get((int)(Math.random()*crossoverPoints.size()));
			LinkedList<EternityPiece> kid1 = new LinkedList<EternityPiece>();
			LinkedList<EternityPiece> kid2 = new LinkedList<EternityPiece>();
			for (int i=0; i<=cross; i++){
				kid1.add(eg1.getPiece(i));
				kid2.add(eg2.getPiece(i));
			}
			boolean flag = true;
			for (int i=cross+1; flag && i < eg2.getSize(); i++){
				EternityPiece p = eg2.getPiece(i);
				if (kid1.contains(p)){
					flag = false;
				} else {
					kid1.add(p);
				}
			}
			fillUpInnerFrame(kid1);
			flag = true;
			for (int i=cross+1; flag && i < eg1.getSize(); i++){
				EternityPiece p = eg1.getPiece(i);
				if (kid2.contains(p)){
					flag = false;
				} else {
					kid2.add(p);
				}
			}
			fillUpInnerFrame(kid2);
			//			System.out.println(crossoverPoints);
			return new Genotype[]{new EternityGenotype(kid1), new EternityGenotype(kid2)};
		} else {
			nocrossover++;
			return new Genotype[]{g1.clone(), g2.clone()};
		}

	}

	@Override
	public double fitness(Genotype g) {
		return ((EternityGenotype)g).getSize();
	}

	@Override
	public List<Genotype> getInitialPopulation(int populationSize) {
		return getInitialRandomPopulation(populationSize);
	}

	@Override
	public List<Genotype> getMatingPool(List<Genotype> population, int populationSize) {
		return getMatingPoolRouletteWheel(population, populationSize);
	}

	@Override
	public Genotype mutate(Genotype g, double Pm) {
		if (Math.random()<Pm){
			EternityGenotype eg = (EternityGenotype)g;
			double third = (eg.getSize()/3.0);
			int rand = (int)(Math.random()*third+2*third);
			List<EternityPiece> newList1 = new LinkedList<EternityPiece>();
			for (int i=0; i<Math.min(rand,60); i++){
				newList1.add(eg.getPiece(i));
			}
			newList1 = fillUpInnerFrame(newList1);
			return new EternityGenotype(newList1);
		} else return g;
	}

	@Override
	public void updateFitness(List<Genotype> population) {
	}

	private List<EternityPiece> filter(int requestedColor1, int offset, int requestedColor2, List<EternityPiece> badList){
		return filter(EternityPiece.colormap.get(requestedColor1), requestedColor1, offset, requestedColor2, badList);
	}

	private List<EternityPiece> compundFilter(int requestedColor1, int offset, int requestedColor2, int offset2, int requestedColor3, List<EternityPiece> badList){
		return filter(EternityPiece.colormap.get(requestedColor1), requestedColor1, offset, requestedColor2, offset2, requestedColor3, badList);
	}

	private List<EternityPiece> filter(List<EternityPiece> org, int baseColor, int offset, int requestedColor, int offset2,	int requestedColor2, List<EternityPiece> badList){
		Set<EternityPiece> ans = new HashSet<EternityPiece>();
		for (EternityPiece e: org){
			if (!badList.contains(e) && !forbidden.contains(e) && e.fits(baseColor, offset, requestedColor, offset2, requestedColor2)){
				ans.add(e);
			}
		}
		return new LinkedList<EternityPiece>(ans);
	}

	private List<EternityPiece> filter(List<EternityPiece> org, int baseColor, int offset, int requestedColor, List<EternityPiece> badList){
		Set<EternityPiece> ans = new HashSet<EternityPiece>();
		for (EternityPiece e: org){
			if (!badList.contains(e) && !forbidden.contains(e) && e.fits(baseColor, offset, requestedColor)){
				ans.add(e);
			}
		}
		return new LinkedList<EternityPiece>(ans);
	}

	public static void maxFrame(){
		List<Integer> gray1 = new LinkedList<Integer>();
		List<Integer> gray2 = new LinkedList<Integer>();
		int size = 60;
		for (int i=0; i<size; i++){
			gray1.add(EternityPiece.GRAY);
		}
		for (int i=0; i<4; i++){
			gray2.add(EternityPiece.GRAY);
		}

		List< List<EternityPiece> > sol = new LinkedList<List<EternityPiece> >();
		int f = 0;

		boolean flag = true;

		List<EternityPiece> gen;
		List<EternityPiece> forbidden = new LinkedList<EternityPiece>();
		List<Integer> colors = gray1;
		List<Integer> extras = gray2;
		List<Integer> colors2 = new LinkedList<Integer>();
		List<Integer> extras2 = new LinkedList<Integer>();

		while(flag){
			GeneticAlgorithm b = new GeneticAlgorithm(new EternityPhenotype(colors, extras, forbidden));
			b.init(100);
			Genotype solution = b.runAlgorithm(100, 0.7, 0.1,0, "eternity"+f+".txt");

			gen = ((EternityGenotype)solution).gen;
			sol.add(gen);

			flag = flag && gen.size()==60-f*8;

			int fsize = gen.size();
			int corner = fsize/4;
			int color1 = 0, color2 = 0;

			for (int i=0; i<fsize && flag; i++){
				if (i%corner == 0){
					color1 = colors.get(i);
					color2 = extras.get(i/corner);
				} else {
					EternityPiece p = gen.get(i-1);
					color1 = p.getColor(color1, 1, color2, 2);	//TODO problem if it has 2 colors of the same kinds
					color2 = colors.get(i);
					if (i%corner == 1){
						extras2.add(gen.get(i).getColor(color1, 1, color2, 3));
					} else {
						colors2.add(gen.get(i).getColor(color1, 1, color2, 3));					
					}
				}
			}
			forbidden.addAll(gen);
			colors = colors2;
			extras = extras2;
			colors2 = new LinkedList<Integer>();
			extras2 = new LinkedList<Integer>();
			f++;
		}
		System.out.println("Solutions:");

		for (int i=0; i<sol.size(); i++){
			System.out.println("Frame "  + i + ": " + sol.get(i));
		}
		System.out.println(nocrossover);
	}

	private static PrintWriter openFile(String filename){
		try{
			File f = new File(filename);
			f.delete();
			f.createNewFile();
			PrintWriter out = new PrintWriter(f);
			return out;
		}catch (Exception e) {
			return null;
		}
	}

	public static void maxFrame2(){
		PrintWriter exceptions = openFile("exceptions.txt");
		List<Integer> gray1 = new LinkedList<Integer>();
		List<Integer> gray2 = new LinkedList<Integer>();
		int size = 60;
		for (int i=0; i<size; i++){
			gray1.add(EternityPiece.GRAY);
		}
		for (int i=0; i<4; i++){
			gray2.add(EternityPiece.GRAY);
		}

		int f = 0;

		boolean flag = true;


		List<SolutionTreeNode> sols = new LinkedList< SolutionTreeNode >();
		List<SolutionTreeNode> sols2 = new LinkedList< SolutionTreeNode >();

		for (int i=0; i<100; i++){
			System.out.print(i);
			GeneticAlgorithm b = new GeneticAlgorithm(new EternityPhenotype(gray1, gray2, new LinkedList<EternityPiece>()));
			b.init(100);
			Genotype solution = b.runAlgorithm(100, 0.7, 0.1,0, "eternity"+f+".txt");

			List<EternityPiece> gen = ((EternityGenotype)solution).gen;
			if (gen.size()==60-f*8){
				sols.add(new SolutionTreeNode(null, gen, 60, i));
			}
		}
		SolutionTreeNode best = sols.get(0);
		int bestVal = 60;

		flag = !sols.isEmpty();
		f++;
		while(flag){
			bestVal = 0;
			flag = false;
			sols2 = new LinkedList< SolutionTreeNode >();
			for (int j=0; j<sols.size(); j++){
				for (int i=0; i<20; i++){
					try{
						SolutionTreeNode parent = sols.get(j);
						if (parent.isSolution()){
							System.out.print(j + "/"+sols.size()+"," + i + " - ");
							GeneticAlgorithm b = new GeneticAlgorithm(new EternityPhenotype(parent.getColors(), parent.getExtras(), parent.getForbidden()));
							b.init(100);
							Genotype solution = b.runAlgorithm(100, 0.7, 0.1, 0, "eternity"+f+"-"+i+"-"+j+".txt");

							List<EternityPiece> gen = ((EternityGenotype)solution).gen;
							SolutionTreeNode tmp = new SolutionTreeNode(parent, gen, 60-f*8, j);
							sols2.add(tmp);
							if (gen.size()==60-f*8){
								flag = true;
							}
							if (gen.size()>bestVal){
								bestVal = gen.size();
								best = tmp;
							}
						}
					} catch (Exception e){
						e.printStackTrace();
						exceptions.println(e.getLocalizedMessage());
						exceptions.println(e.getMessage());
						exceptions.println(Arrays.toString(e.getStackTrace()));
					}
				}
			}
			sols = sols2;
			f++;
		}
		System.out.println("Solutions:");
		System.out.println(sols.size());
		for (int i=0; i<sols.size(); i++){
			System.out.println("XXXXXXXXXXXXXXXXX");
			for (SolutionTreeNode t = sols.get(i); t!=null; t=t.parent){
				System.out.println("Frame "  + i + "-"+t.getIndex() +": " + t.getSolution());
			}
		}
		
		for (SolutionTreeNode t = best; t!=null; t=t.parent){
			System.out.println("Best("+sols.indexOf(best) + "-" + t.getIndex() +"): " + t.getSolution());
		}
		System.out.println(nocrossover);
	}

	public static void main(String[] args) {
		maxFrame2();
	}

	//	public static void main(String[] args) {
	//		List<Integer> gray1 = new LinkedList<Integer>();
	//		List<Integer> gray2 = new LinkedList<Integer>();
	//		int size = 60;
	//		for (int i=0; i<size; i++){
	//			gray1.add(EternityPiece.GRAY);
	//		}
	//		for (int i=0; i<4; i++){
	//			gray2.add(EternityPiece.GRAY);
	//		}
	////		int totalMax = 0;
	//		EternityPhenotype proto = new EternityPhenotype(gray1, gray2, new LinkedList<EternityPiece>());
	//		for (int i=0; i<2000; i++){
	////			System.out.println(i);
	//			EternityGenotype p1 = (EternityGenotype)(proto.createRandomInstance());
	////			EternityGenotype p2 = (EternityGenotype)(proto.createRandomInstance());
	//			//			p1 = (EternityGenotype)proto.mutate(p1, 1);
	//			//			p2 = (EternityGenotype)proto.mutate(p2, 1);
	//			//			if (p1.getFitness()==size-1 || p1.getSize()==size){ System.out.println(p1); System.out.println(proto.filter(EternityPiece.colormap.get(EternityPiece.GRAY), EternityPiece.GRAY, 0, EternityPiece.GRAY, p1.gen));}
	//			//			if (p2.getFitness()==size-1 || p2.getSize()==size){ System.out.println(p2); System.out.println(proto.filter(EternityPiece.colormap.get(EternityPiece.GRAY), EternityPiece.GRAY, 0, EternityPiece.GRAY, p2.gen));}
	//			//			int max = Math.max((int)p1.getFitness(), (int)p2.getFitness());
	//			//			totalMax = Math.max(max, totalMax);
	////			System.out.println("XXXXXXXXX");
	////			System.out.println(p1);
	////			System.out.println(p2);
	////			proto.crossover(p1, p2);
	////			System.out.println("XXXXXXXXX");
	//			//			p = (EternityGenotype)(proto.mutate(p, 1));
	//			//			Genotype[] co = new EternityPhenotype().crossover(p1, p2);
	//			//			EternityGenotype p = (EternityGenotype)co[0];
	//						if (p1.getSize()==60){
	//						System.out.println(p1);
	//						System.out.println(i);
	//						}
	//			//			System.out.println(p.rep.size() + " - " + p.rep.toString());
	//			//			p = (EternityGenotype)co[1];
	//			//			System.out.println(p.rep.size() + " - " + p.rep.toString());
	//		}
	//		//		System.out.println(totalMax);
	//		//		System.out.println(proto.borderFilter(EternityPiece.GRAY, new LinkedList<EternityPiece>()));
	//	}

}
